import { effect } from "../reactivity/effect";
import { EMPTY_OBJ, isObject } from "../shared/index";
import { ShapeFlags } from "../shared/ShapeFlags";
import { createComponentInstance, setupComponent } from "./component";
import { shouldUpdateComponent } from "./componentUpdateUtils";
import { createAppAPI } from "./createApp";
import { queueJobs } from "./scheduler";
import { Fragment, Text } from "./vnode";


export function createRenderer(options) {

    const {
        createElement: hostCreateElement,
        patchProp: hostPatchProp,
        insert: hostInsert,
        remove: hostRemove,
        setElementText:hostSetElementText,
    } = options;

    function render(vnode, container) {
        // patch方法
        patch(null,vnode, container, null,null);
    }
    // n1 -> 老的
    // n2 -> 新的
    function patch(n1:any,n2: any, container: any, parentComponent,anchor) {

        // 去处理组件
        // 判断 vnode 是不是 element类型
        // 是element 那么就处理 element
        // 如何区分是element类型还是component类型
        // console.log(vnode.type)
        const { type, shapeFlag } = n2;

        // Fragment -> 只渲染children
        switch (type) {
            case Fragment:
                processFragment(n1,n2, container, parentComponent,anchor);
                break;
            case Text:
                processText(n1,n2, container);
                break;
            default:
                if (shapeFlag & ShapeFlags.ELEMENT) {
                    processElement(n1,n2, container, parentComponent,anchor);
                } else if (shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
                    processComponent(n1,n2, container, parentComponent,anchor);
                }
                break;

        }
    }
    function processText(n1,n2: any, container: any) {
        const { children } = n2;
        const textNode = (n2.el = document.createTextNode(children));
        container.append(textNode)
    }

    function processFragment(n1,n2: any, container: any, parentComponent,anchor) {
        // Implement
        mountChildren(n2.children, container, parentComponent,anchor);
    }

    function processElement(n1,n2: any, container: any, parentComponent,anchor) {
        if (!n1){
            mountElement(n2, container, parentComponent,anchor);
        } else {
            patchElement(n1,n2,container,parentComponent,anchor);
        }
        
    }
    function patchElement(n1,n2,container,parentComponent,anchor){
        console.log("patchElement")
        
        const oldProps = n1.props || EMPTY_OBJ;
        const newProps = n2.props || EMPTY_OBJ;
        const el = (n2.el = n1.el);
        //children
        patchChildren(n1,n2,el,parentComponent,anchor);
        //props
        patchProps(el,oldProps,newProps);
        
        
        
    }
    function patchChildren(n1,n2,container,parentComponent,anchor){
        const prevShapeFlag = n1.shapeFlag;
        const c1 = n1.children;
        const {shapeFlag} = n2;
        const c2 = n2.children;
        if (shapeFlag & ShapeFlags.TEXT_CHILDREN){
            if ( prevShapeFlag & ShapeFlags.ARRAY_CHILDREN){
                // 1.把老的chidren清空
                unmountChildren(n1.children);
            } 
            if (c1 !== c2){
                // 2.设置text
                hostSetElementText(container,c2);
            }
            
        } else {
            // new array
            if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN){
                hostSetElementText(container,"")
                mountChildren(c2,container,parentComponent,anchor)
            } else {
                // array diff aray
                patchKeyChildren(c1,c2,container,parentComponent,anchor);
            }
        }
    }
    function patchKeyChildren(c1,c2,container,parentComponent,parentAnchor){
        const l2 = c2.length;
        let i=0;
        let e1 = c1.length - 1;
        let e2 = l2 - 1;
        function isSomeVNodeType(n1,n2){
            // type
            // key
            return n1.type === n2.type && n1.key === n2.key;
        }
        // 左侧
        while (i <= e1 && i<= e2){
            const n1 = c1[i];
            const n2 = c2[i];
            if (isSomeVNodeType(n1,n2)){
                patch(n1,n2,container,parentComponent,parentAnchor);
            } else {
                break;
            }
            i++;
        }
        //右侧
        while (i<=e1 && i<=e2){
            const n1 = c1[e1];
            const n2 = c2[e2];
            if (isSomeVNodeType(n1,n2)){
                patch(n1,n2,container,parentComponent,parentAnchor);
            } else {
                break;
            }
            e1--;
            e2--;
        }

        // 新的比老的多 创建
        if (i>e1){
            if (i<=e2){
                
                const nextPos = e2 + 1;
                const anchor = nextPos<l2?c2[nextPos].el:null;
                while (i<=e2){
                    patch(null,c2[i],container,parentComponent,anchor)
                    i++;
                }
                
            }
        } else if (i>e2){
            while (i<=e1){
                hostRemove(c1[i].el);
                i++;
            }
        } else {
            // 中间对比
            let s1 = i;
            let s2 = i;
            const toBePatched = e2-s2+1;
            let patched = 0;
            //映射表，使查找某个child的时间效率为O(1)
            const keyToNewIndexMap = new Map()
            const newIndexToOldIndexMap = new Array(toBePatched)
            let moved = false;
            let maxNewIndexSoFar = 0;
            for (let i=0;i<toBePatched;i++)
                newIndexToOldIndexMap[i] = 0; //0表示初始状态，若为0表示还未建立映射关系，即该节点不存在


            for (let i = s2;i<=e2;i++){
                const nextChild = c2[i];
                keyToNewIndexMap.set(nextChild.key,i);
            }

            for (let i=s1;i<=e1;i++){
                const prevChild = c1[i];
                //patched是已经渲染的节点数，当新的所有节点都渲染后，就不需要再执行下面的逻辑，直接删掉就好了
                if (patched >= toBePatched){
                    hostRemove(prevChild.el);
                    continue;
                }

                let newIndex;
                // null undefined
                if (prevChild.key != null){
                    newIndex = keyToNewIndexMap.get(prevChild.key);

                } else {
                    for (let j = s2; j<= e2;j++){
                        if (isSomeVNodeType(prevChild,c2[j])){
                            newIndex = j;
                            break;
                        }
                    }
                }
                if (newIndex === undefined){
                    hostRemove(prevChild.el);
                } else {

                    //看看是否需要移动位置，如果索引不是递增的就需要移动
                    if (newIndex >= maxNewIndexSoFar){
                        maxNewIndexSoFar = newIndex;
                    } else {
                        moved = true;
                    }


                    newIndexToOldIndexMap[newIndex - s2] = i+1;
                    patch(prevChild,c2[newIndex],container,parentComponent,null);
                    patched ++;
                }
            }
          
            const increasingNewIndexSequence = moved?getSequence(newIndexToOldIndexMap):[];
            let j = increasingNewIndexSequence.length - 1;
            debugger;
            for (let i = toBePatched - 1;i>=0;i--){ // 倒序解决插入会使顺序混乱的问题

                const nextIndex = i + s2;
                const nextChild = c2[nextIndex];
                const anchor = nextIndex + 1 < l2? c2[nextIndex + 1].el:null;

                if (newIndexToOldIndexMap[i] === 0) {
                    //创建新节点
                    patch(null,nextChild,container,parentComponent,anchor);
                } else if (moved){
                    if (j<0 || i!== increasingNewIndexSequence[j]){
                        // console.log("移动位置")
                        hostInsert(nextChild.el,container,anchor)
                    } else {
                        j--;
                    }
                }

                
            }
        }
        

    }
    function unmountChildren(children){
        for (let i=0;i<children.length;i++){
            const el = children[i].el;
            //remove
            hostRemove(el);

        }
    }
    

    function patchProps(el,oldProps,newProps){
        if (oldProps !== newProps){
            for (const key in newProps) {
                const prevProp = oldProps[key];
                const nextProp = newProps[key];
    
                if (prevProp !== nextProp) {
                    hostPatchProp(el,key,prevProp,nextProp);
                }
            }
            if (oldProps !== EMPTY_OBJ){
                for (const key in oldProps) {
                    if (!(key in newProps)){
                        hostPatchProp(el,key,oldProps[key],null);
                    }
                }
            }
            
        }
        

    }

    function processComponent(n1,n2: any, container: any, parentComponent,anchor) {
        if (!n1){
            mountComponent(n2, container, parentComponent,anchor);
        } else {
            updateComponent(n1,n2);
        }
        
    }

    function updateComponent(n1,n2){
        const instance = (n2.component = n1.component);
        if (shouldUpdateComponent(n1,n2)){
            
            instance.next = n2;
            instance.update();
        } else {
            n2.el = n1.el;
            instance.vnode = n2;
        }
        
    }

    function mountElement(vnode: any, container: any, parentComponent,anchor) {

        //canvas
        // new Element()

        const el = (vnode.el = hostCreateElement(vnode.type));

        //string array
        const { children } = vnode;
        const { shapeFlag } = vnode;
        if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
            el.textContent = children;
        } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
            //vnode
            mountChildren(vnode.children, el, parentComponent,anchor)
        }

        const { props } = vnode;

        //props
        for (const key in props) {
            const val = props[key];
            // 具体的 click =》通用
            // on + Event name
            // const isOn = (key:string) => /^on[A-Z]/.test(key)
            // if (isOn(key)) {
            //     const event = key.slice(2).toLowerCase()
            //     el.addEventListener(event,val);
            // } else {
            //     el.setAttribute(key,val);
            // }
            hostPatchProp(el, key, null,val);

        }

        //canvas
        //el.x = 10

        // container.append(el);

        // canvas
        // addChild()
        hostInsert(el, container,anchor);
    }
    function mountChildren(children, container, parentComponent,anchor) {
        children.forEach(v => {
            patch(null,v, container, parentComponent,anchor);
        })
    }
    function mountComponent(initialVNode: any, container: any, parentComponent,anchor) {
        const instance = (initialVNode.component = createComponentInstance(initialVNode, parentComponent));
        setupComponent(instance);

        setupRenderEffect(instance, initialVNode, container,anchor);
    }

    function setupRenderEffect(instance: any, vnode: any, container: any,anchor) {
        //将render函数里的虚拟节点，此处为instance放入effect，当响应式变量发生改变时，会重新触发渲染，
        // 此时区分：如果是第一次渲染初始化，否则，对比新旧节点，然后更新
        instance.update = effect(() => {
            if (!instance.isMounted) {
                console.log('init');
                const { proxy } = instance;
                //第二个proxy解决template转换成render函数后的this指向问题
                const subTree = (instance.subTree = instance.render.call(proxy,proxy));
                // vnode -> patch
                // vnode ->element -> mountElement

                patch(null,subTree, container, instance,anchor)

                //element -> mount 挂载el的时机是所有的subTree都处理完之后

                vnode.el = subTree.el;

                instance.isMounted = true;
            }
            else {
                console.log('update');
                // 需要一个 vnode
                const {next,vnode} = instance;
                if (next){
                    next.el = vnode.el;
                    updateComponentPreRender(instance,next);
                }


                const { proxy } = instance;
                const subTree = instance.render.call(proxy,proxy);
                const prevSubTree = instance.subTree;
                instance.subTree = subTree;
                patch(prevSubTree,subTree, container, instance,anchor)

            }


        },{
            scheduler(){
                console.log("update - scheduler")
                queueJobs(instance.update);
            }
        })

    }
    

    return {
        createApp: createAppAPI(render)
    }
}

function updateComponentPreRender(instance,nextVNode){
    instance.vnode = nextVNode;
    instance.next = null;
    
    instance.props = nextVNode.props;

}   

function getSequence(arr){
    const p = arr.slice();
    const result = [0];
    let i,j,u,v,c;
    const len = arr.length;
    for (i = 0;i<len;i++){
        const arrI = arr[i];
        if (arrI !== 0 ){
            j = result[result.length - 1];
            if (arr[j] < arrI){
                p[i] = j;
                result.push(i);
                continue;
            }
            u = 0
            v = result.length - 1;
            while (u<v){
                c = (u+v) >>1;
                if (arr[result[c]] < arrI){
                    u = c + 1;

                } else {
                    v = c;
                }
            }
            if (arrI < arr[result[u]]){
                if (u>0) {
                    p[i] = result[u-1];
                }
                result[u] = i;
            }
        }
        
    }
    u = result.length;
    v = result[u-1];
    while (u-- > 0) {
        result[u] = v;
        v = p [v];
    }
    return result;
}


